/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
   Copyright (C) 2011 Red Hat, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

/*
 * Taken from xserver os/backtrace.c:
 * Copyright (C) 2008 Red Hat, Inc.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#ifndef __MINGW32__
#include <sys/wait.h>
#include <signal.h>
#else
#include <windows.h>
#include <imagehlp.h>
#endif

#include "spice_common.h"

#define GSTACK_PATH "/usr/bin/gstack"
#ifndef __MINGW32__
#define HAVE_EXECINFO_H	1
#else
#define HAVE_EXECINFO_H	0
#endif

#if HAVE_EXECINFO_H
#include <execinfo.h>
#define EXCEPTION_FILE	"/tmp/exception"

static void spice_backtrace_backtrace(void)
{
    void *array[100];
    int size;

    size = backtrace(array, sizeof(array)/sizeof(array[0]));
    FILE* fp = fopen(EXCEPTION_FILE, "a+");
    backtrace_symbols_fd(array, size, fp->_fileno);
    fclose(fp);
}
#endif

/* XXX perhaps gstack can be available in windows but pipe/waitpid isn't,
 * so until it is ported properly just compile it out, we lose the
 * backtrace only. */
#ifndef __MINGW32__
static int spice_backtrace_gstack(void)
{
    pid_t kidpid;
    int pipefd[2];

    if (pipe(pipefd) != 0) {
        return -1;
    }

    kidpid = vfork();

    if (kidpid == -1) {
        /* ERROR */
        return -1;
    } else if (kidpid == 0) {
        /* CHILD */
        char cmd[64];
        snprintf(cmd, sizeof(cmd), "gstack %d >> %s", getppid(), EXCEPTION_FILE);
		execle("/bin/sh", "sh", "-c", cmd, NULL, NULL);

        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        dup2(pipefd[1],STDOUT_FILENO);
        close(STDERR_FILENO);

        exit(1);
    } else {
        /* PARENT */
        char btline[256];
        int kidstat;
        int bytesread;
        int done = 0;

        close(pipefd[1]);

        while (!done) {
            bytesread = read(pipefd[0], btline, sizeof(btline) - 1);

            if (bytesread > 0) {
                btline[bytesread] = 0;
                fprintf(stderr, "%s", btline);
            }
            else if ((bytesread == 0) ||
                     ((errno != EINTR) && (errno != EAGAIN))) {
                done = 1;
            }
        }
        close(pipefd[0]);
        waitpid(kidpid, &kidstat, 0);
        if (kidstat != 0)
            return -1;
    }
    return 0;
}

void parse_signal_param(void* param)
{
	if (param == NULL) {
		return;
	}

	char *p_msg = NULL;
	char stack_msg[1024] = {0};
	char cmd[64] = {0};
	long file_size = 0;
	time_t now;
	struct tm *tblock;

	p_msg = stack_msg;
	now = time(NULL);
	tblock = localtime(&now);
	sprintf(p_msg, "\nLocal time:%s", asctime(tblock));

	FILE *fp = fopen(EXCEPTION_FILE, "a+" );
	if (fp != NULL) {
		fputs(stack_msg, fp);
		file_size = ftell(fp);
		fclose(fp);
	}
	if (file_size > 1024 * 1024) {
		sprintf(cmd, "mv -f %s %s.bak", EXCEPTION_FILE, EXCEPTION_FILE);
		execle("/bin/sh", "sh", "-c", cmd, NULL, NULL);
	}
}

struct sigaction oldactup;
struct sigaction oldactint;
struct sigaction oldactquit;
struct sigaction oldactill;
struct sigaction oldactabrt;
struct sigaction oldactfpe;
struct sigaction oldactsegv;
struct sigaction oldactpipe;
struct sigaction oldactalrm;
struct sigaction oldactterm;
struct sigaction oldactbus;
struct sigaction oldactkill;
struct sigaction oldactpoll;
struct sigaction oldactpwr;
struct sigaction oldactstop;
struct sigaction oldactsys;
struct sigaction oldactrap;
struct sigaction oldactsqlerr;
static void exception_signal_handler(int signum, siginfo_t *info, void *param)
{
    switch(signum)
    {
        case SIGHUP:
            sigaction(SIGHUP, &oldactup, NULL);
            break;
        case SIGINT:
            sigaction(SIGINT, &oldactint, NULL);
            break;
        case SIGQUIT:
            sigaction(SIGQUIT, &oldactquit, NULL);
            break;
        case SIGILL:
            sigaction(SIGILL, &oldactill, NULL);
            break;
        case SIGABRT:
            sigaction(SIGABRT, &oldactabrt, NULL);
            break;
        case SIGFPE:
            sigaction(SIGFPE, &oldactfpe, NULL);
            break;
        case SIGSEGV:
            sigaction(SIGSEGV, &oldactsegv, NULL);
            break;
        case SIGPIPE:
            sigaction(SIGPIPE, &oldactpipe, NULL);
            break;
        case SIGALRM:
            sigaction(SIGALRM, &oldactalrm, NULL);
            break;
        case SIGTERM:
            sigaction(SIGTERM, &oldactterm, NULL);
            break;
		case SIGBUS:
            sigaction(SIGBUS, &oldactbus, NULL);
			break;
		case SIGKILL:
            sigaction(SIGKILL, &oldactkill, NULL);
			break;
		case SIGPOLL:
            sigaction(SIGPOLL, &oldactpoll, NULL);
			break;
		case SIGPWR:
            sigaction(SIGPWR, &oldactpwr, NULL);
			break;
		case SIGSTOP:
            sigaction(SIGSTOP, &oldactstop, NULL);
			break;
		case SIGSYS:
            sigaction(SIGSYS, &oldactsys, NULL);
			break;
		case SIGTRAP:
            sigaction(SIGTRAP, &oldactrap, NULL);
			break;
        default:
            break;
    }

	spice_backtrace();
	parse_signal_param(param);
    raise(signum);
	exit(signum);
}

void spice_backtrace(void)
{
    int ret = -1;
    if (!access(GSTACK_PATH, X_OK)) {
        ret = spice_backtrace_gstack();
    }
    if (ret != 0) {
        spice_backtrace_backtrace();
    }
}

void exception_signal_handler_init(void)
{
    struct sigaction act;

    act.sa_sigaction = &exception_signal_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART | SA_SIGINFO;

    if (sigaction(SIGHUP, &act, &oldactup)  ||
		sigaction(SIGINT, &act, &oldactint)  ||
        sigaction(SIGQUIT, &act, &oldactquit) ||
        sigaction(SIGILL, &act, &oldactill)  ||
        sigaction(SIGABRT, &act, &oldactabrt) ||
        sigaction(SIGFPE, &act, &oldactfpe)  ||
        sigaction(SIGSEGV, &act, &oldactsegv) ||
        sigaction(SIGPIPE, &act, &oldactpipe) ||
        sigaction(SIGALRM, &act, &oldactalrm) ||
        sigaction(SIGTERM, &act, &oldactterm) ||
		sigaction(SIGBUS, &act, &oldactbus)   ||
		//sigaction(SIGKILL, &act, &oldactkill)   ||
		sigaction(SIGPOLL, &act, &oldactpoll)   ||
		sigaction(SIGPWR, &act, &oldactpwr)   ||
		//sigaction(SIGSTOP, &act, &oldactstop)   ||
		sigaction(SIGSYS, &act, &oldactsys)   ||
		sigaction(SIGTRAP, &act, &oldactrap))
    {
        fprintf(stdout, "SIGACTION FUNCTION FAILED. Signal handling will not be available.");
    }
}
#else
void spice_backtrace(void)
{
}

void exception_signal_handler_init(void)
{
}
#endif
